package org.dk.poitalk.service.entity.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;

import javax.persistence.Query;

import org.dk.poitalk.dao.impl.GenericDaoImpl;
import org.dk.poitalk.exception.DuplicateEntityException;
import org.dk.poitalk.model.EntityClient;
import org.dk.poitalk.model.TipoEntityClient;
import org.dk.poitalk.service.entity.EntityService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.memcache.Expiration;
import com.jappstart.service.cache.CacheService;

@Service
public class EntityServiceImpl extends GenericDaoImpl<EntityClient, Serializable>
		implements EntityService, CacheService<EntityClient> {

	private static final int DEFAULT_EXPIRATION = 3600;

	private static final Logger logger = Logger
			.getLogger(EntityServiceImpl.class.getName());
	/**
	 * TODO Añadir parametros a una query Global solo funciona cuando HACE ANDS Y =
	 * @param paramValue
	 * @param sQuery
	 * @return
	 */
	private String addParameters(Map<String, String> mapParamValue, String sQuery){
		if ((mapParamValue != null) && (mapParamValue.size() > 0)){
			sQuery = sQuery + " WHERE";
			int i = 1;
			for (Entry<String, String> entry : mapParamValue.entrySet()) {
				if ((i>=2) && (i<=mapParamValue.size())) { sQuery = sQuery + " AND";}
				sQuery = sQuery + " " + entry.getKey() + " = :" + entry.getKey();	
				i++;
			}	
		}
		logger.info(String.format("addParameters ha generado la siguiente query  = %s ",
				sQuery));
		return sQuery;
	}
	
	private Query setParameter (Map<String, String> mapParamValue, Query query){
		if ((mapParamValue != null) && (mapParamValue.size() > 0)){
			for (Entry<String, String> entry : mapParamValue.entrySet()) {
				query.setParameter(entry.getKey(),entry.getValue());
			}	
		}
		return query;
	}
	
	public void putEntityCache(EntityClient entityClient){
		getMemcacheService().put(entityClient.getIdPlace(), entityClient,
		Expiration.byDeltaSeconds(DEFAULT_EXPIRATION));
		logger.info(String.format("El objeto %s ha sido putted en la cache con esta key = %s",entityClient,entityClient.getIdPlace()));		
	}
	
	public EntityClient getEntityCache(EntityClient entityClient){
		EntityClient rEntityClient = (EntityClient) getMemcacheService().get(entityClient.getIdPlace());
		logger.info(String.format("El objeto %s ha sido recogido de la cache",rEntityClient));
		return rEntityClient;
	}	
	
	public void removeEntityCache(EntityClient entityClient){
		Boolean cacheDelete =getMemcacheService().delete(entityClient.getIdPlace());
		logger.info(String.format("El objeto %s ha sido borrada de la cache con estado = %s",entityClient,cacheDelete.toString()));
	}	
	
	@Override
	public List<EntityClient> search(Map<String, String> mapParamValue) {
		EntityClient rEntityClient = null;
				
		String sQuery = SELECT_ENTITY_PARAMS;
		sQuery = this.addParameters(mapParamValue, sQuery);
		Query query = entityManager.createQuery(sQuery);
		query = this.setParameter(mapParamValue, query);
		List<EntityClient> results = null;
		try {
			results =  query.getResultList();
    		logger.info(String.format("La search devuelve  %d filas de size ",
					results.size()));					
		} catch (javax.persistence.NoResultException noResultException) {
			logger.info(String.format(
					"La saveOrUpdate:query:NoResultException =  %s ",
					noResultException.toString()));	
			results = new ArrayList<EntityClient> ();
		}


		return results;
	}
	
	@Override
	public EntityClient findById(HashMap<String, String> paramValue) {

		EntityClient rEntityClient = null;
		Query query;
		
		String idPlace = paramValue.get(IDPLACE);
		if (idPlace != null) {
			final EntityClient entityClientCached = this.getEntityCache(new EntityClient(idPlace, TipoEntityClient.GOOGLEPLACE,null));
			if (entityClientCached != null) {
				rEntityClient = entityClientCached;
			} 
		} 

		if (rEntityClient ==  null){
			
			query = entityManager.createQuery(SELECT_ENTITY_IDPLACE);
			query = this.setParameter(paramValue, query);
			
			try {
				rEntityClient = (EntityClient) query.getSingleResult();
				logger.info(String.format("La findById =  %s ",
						rEntityClient));			
			} catch (javax.persistence.NoResultException e) {
				logger.info(String.format(
						"La saveOrUpdate:query:NoResultException =  %s ",
						e.toString()));			
			}

		}
		return rEntityClient;
	}
	
	@Override
	@Transactional
	public void save(EntityClient entityClient) {
	
		final Query query = entityManager.createQuery(SELECT_ENTITY_IDPLACE);
		String idPlace = entityClient.getIdPlace();
		query.setParameter(IDPLACE, idPlace);

		@SuppressWarnings("unchecked")
		final List<EntityClient> results = query.getResultList();
		logger.info(String.format("%d Filas devueltas ", results.size()));
		if (results != null && !results.isEmpty()) {
			throw new DuplicateEntityException();
		}
		Key keyExternal = (entityClient.getUserAccount() != null)?entityClient.getUserAccount().getKey():null;
		entityClient.setKey(EntityClient.getNewKeyExternal(keyExternal, entityClient.getIdPlace()));
		logger.info(String.format(
				"Entity %s tosave:save with key: %s",entityClient, entityClient.getKey()));			
		entityManager.persist(entityClient);
		this.putEntityCache(entityClient);	
	}

	/**
	 * No puede gestionar entityClientFIND.setUserAccount
	 * SE ha creado un addUserAccount para el hecho
	 */
	@Override
	@Transactional
	public EntityClient saveOrUpdate(EntityClient entityClient) {

		EntityClient rEntityClient;
		HashMap<String,String> paramValue= new HashMap<String, String>();
		paramValue.put(EntityService.IDPLACE,entityClient.getIdPlace());		
		EntityClient entityClientFIND = this.findById(paramValue);
		logger.info(String.format("Entity to saveOrUpdate:Finded = %s",
				(entityClientFIND==null)?"NULL":entityClientFIND.toString()));		
		if ((entityClientFIND != null)) {
			entityClientFIND.setTipoEntityClient(entityClient.getTipoEntityClient());	
			entityManager.persist(entityClientFIND);
			this.removeEntityCache(entityClient);		
			this.putEntityCache(entityClientFIND);
			rEntityClient = entityClientFIND;
		} else {
			this.save(entityClient);
			rEntityClient = entityClient;
		}
		return rEntityClient;
	}

	@Override
	@Transactional
	public int delete(EntityClient entityClient) {
		int resultUpdate = 0;
		HashMap<String,String> paramValue= new HashMap<String, String>();
		paramValue.put(EntityService.IDPLACE,entityClient.getIdPlace());	
		EntityClient entityClientFIND = this.findById(paramValue);
		if (entityClientFIND != null){
			try {
				logger.info(String.format("Entity to delete:findById = %s",
						entityClientFIND.toString()));
				final Query queryDelete = entityManager
						.createQuery(DELETE_ENTITY_IDPLACE);
				queryDelete.setParameter(IDPLACE, entityClientFIND.getIdPlace());
				resultUpdate = queryDelete.executeUpdate();
				logger.info(String.format(
						"Entity to delete:Delete:resultUpdate = %s", resultUpdate));
				if (resultUpdate >= 1) {
					this.removeEntityCache(entityClient);
				}
			} catch (javax.persistence.NoResultException e) {
				logger.info(String.format(
						"La saveOrUpdate:query:NoResultException =  %s ",
						e.toString()));
			}
		}
		return resultUpdate;

	}
	
    /**
     * --> Al ser usado desde ClaimController:addx <--
     * getEntityService().addUserAccount(entityClientIN);
     * logger.info(String.format("Entity to addx:saveOrUpdate: %s with Key", entityClientIN.toString(),entityClientIN.getKey()));
     * INFO  DataNucleus.Persistence  - PCMapping.setObject : object com.google.appengine.api.datastore.Key:UserAccount("individuodk")/EntityClient("e91dfd9
	 * d0ac134d207c48cacfa9a3c6e6fe4b8ad") has field 3 that is 1-N bidirectional. 
	 * Have just attached the N side so should really update the reference in the 1 side collection to refer to this attached object
	 * . Not yet implemented
     */

	@Override
	@Transactional
	public Boolean addUserAccount(EntityClient entityClient) {
		Boolean isAdded = false;
		HashMap<String,String> paramValue= new HashMap<String, String>();
		paramValue.put(EntityService.IDPLACE,entityClient.getIdPlace());		
		EntityClient entityClientFIND = this.findById(paramValue);
		logger.info(String.format("Entity to saveOrUpdate:Finded = %s",
				(entityClientFIND==null)?"NULL":entityClientFIND.toString()));		
		if ((entityClientFIND != null)) {
			entityClientFIND.setUserAccount((entityClient.getUserAccount()!=null)?entityClient.getUserAccount():null);			
			entityManager.persist(entityClientFIND);
			this.removeEntityCache(entityClient);		
			this.putEntityCache(entityClientFIND);
			isAdded = true;
		} 
		return isAdded;
	}


}
